package com.newmainsoftech.dao.entity.relation;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareMixin;
import org.aspectj.lang.annotation.Pointcut;

import com.newmainsoftech.aspectjutil.adviseutil.Util;

/**
 * Aspect to control synchronization among method executions in an 
 * <code>{@link EntityBiLinkCollectionProxyImpl}</code> instance.
 * 
 * @author Arata Y.
 */
@Aspect
public class EntityBiLinkCollectionProxyImplAspect {
	// Add ReentrantLock to EntityBiLinkCollectionProxyImpl as Mixin ------------------------
	public static interface LockContainer {
		ReentrantReadWriteLock getLock();
		
		long getReadLockAcquireTimeOut();
		void setReadLockAcquireTimeOut( long duration);
		
		long getWriteLockAcquireTimeOut();
		void setWriteLockAcquireTimeOut( long duration);
	}
	public static class LockContainerImpl implements LockContainer {
		private ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock( true);
			@Override
			public ReentrantReadWriteLock getLock() {
				return reentrantLock;
			}
		public final static long LockAcquireTimeOutDefaultValue = 200L;
			public static long getLockAcquireTimeOutDefaultValue() {
				return LockAcquireTimeOutDefaultValue;
			}
		private long readLockAcquireTimeOut = LockContainerImpl.getLockAcquireTimeOutDefaultValue();
			@Override
			public long getReadLockAcquireTimeOut() {
				return readLockAcquireTimeOut;
			}
			@Override
			public void setReadLockAcquireTimeOut( long duration) {
				this.readLockAcquireTimeOut = duration;
			}
		private long writeLockAcquireTimeOut = LockContainerImpl.getLockAcquireTimeOutDefaultValue();
			@Override
			public long getWriteLockAcquireTimeOut() {
				return writeLockAcquireTimeOut;
			}
			@Override
			public void setWriteLockAcquireTimeOut( long duration) {
				this.writeLockAcquireTimeOut = duration;
			}
	}
	@DeclareMixin( value = "com.newmainsoftech.dao.entity.relation.EntityBiLinkCollectionProxyImpl")
	public LockContainer mixinLockContainer() {
		return new LockContainerImpl();
	}
	// --------------------------------------------------------------------------------------------
	@Pointcut( value 
			= "cflowbelow( execution( com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.new(..)))")
	public static void pointcutForCflowbelowEntityBiLinkCollectionProxyImplConstructioin() {}
	
	@Pointcut( value 
			= "execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.interAdd(..)) " 
			+ "|| execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.interAddAll(..)) " 
			+ "|| execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.interRemove(..)) " 
			+ "|| execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.interRemoveAll(..)) " 
			+ "|| execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.interRetainAll(..))" 
			+ "|| execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.setHolderEntity(..)) " 
			+ "|| execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.setDefaultHolderEntity(..)) " 
			+ "|| execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.setAlterWorkerCollectionConstructor(..)) " 
			+ "|| execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.initOrphanRemovalAttributeFlag()) " 
			+ "|| execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.setOrphanRemovalAttributeOn(..)) " 
			+ "|| execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.isOrphanRemovalAttributeOn()) " 
			+ "|| execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.setOkToNullifyHolderAtRemoval(..)) ")
	public static void pointcutAtExecutionOfWriteSynchronizingMethod() {}
	
	protected Object doAroundExecutionOfWriteSynchronizingMethod( 
			final LockContainer lockContainer, final ProceedingJoinPoint proceedingJoinPoint) 
	throws Throwable {
		final ReentrantReadWriteLock lock = lockContainer.getLock();
		
		if ( !lock.isWriteLockedByCurrentThread()) {
			if ( isReadLockedByCurrentThread()) {
				throw new IllegalMonitorStateException( 
						String.format(
								"Encountered the state of attempting to acquire write lock after having " 
								+ "acquired read lock in %1$s.",
								Thread.currentThread().toString())
						);
			}
			else {
				removeReadLockFlagForCurrentThread();
			}
			
			final WriteLock writeLock = lock.writeLock();
			final long acquireTimeOut = lockContainer.getWriteLockAcquireTimeOut();
			
			boolean isLocked = false;
				try {
					isLocked 
					= writeLock.tryLock( acquireTimeOut, TimeUnit.MILLISECONDS);
				} 
				catch ( InterruptedException exception) {
					Thread.currentThread().interrupt();
				}
			if ( isLocked) {
				try {
					return Util.proceed( proceedingJoinPoint);
				}
				finally {
					writeLock.unlock();
				}
			}
			else {
				throw new IllegalMonitorStateException(
						String.format(
								"Within %1$d [ms], could not acquire write-lock to proceed \"%2$s\".",
								acquireTimeOut, 
								proceedingJoinPoint)
						);
			}
		}
		else {
			return Util.proceed( proceedingJoinPoint);
		}
	}
	
	@Around( value 
			= "pointcutAtExecutionOfWriteSynchronizingMethod() " 
			+ "&& !pointcutForCflowbelowEntityBiLinkCollectionProxyImplConstructioin()")
	public Object aroundExecutionOfWriteSynchronizingMethod( final ProceedingJoinPoint proceedingJoinPoint) 
	throws Throwable {
		Object target = proceedingJoinPoint.getTarget();
			if ( !EntityBiLinkCollectionProxyImpl.class.isInstance( target)) {
				throw new IllegalMonitorStateException(
						String.format(
								"Unexpected to cross-cut join-point in other than %1$s type instance: %2$s",
								EntityBiLinkCollectionProxyImpl.class.getSimpleName(),
								proceedingJoinPoint)
						);
			}
		final LockContainer lockContainer = (LockContainer)target;
		
		return doAroundExecutionOfWriteSynchronizingMethod( lockContainer, proceedingJoinPoint);
	}
	
	@Pointcut( value 
			= "execution( !static * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.*(..)) " 
			+ "&& !pointcutAtExecutionOfWriteSynchronizingMethod() " 
			+ "&& !execution( * com.newmainsoftech.dao.entity.relation" 
				+ ".EntityBiLinkCollectionProxyImpl.invoke(..))" 
			+ "&& !execution( * com.newmainsoftech.dao.entity.relation" 
				+ ".EntityBiLinkCollectionProxyImpl.linkHoldeeWithDefaultHolder(..))" 
			+ "&& !execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.readOrphanRemovalAttribute(..))" 
			+ "&& !execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImpl.getLogger()) ")
	public static void pointcutAtExecutionOfReadSynchronizingMethod() {}
	
	@Pointcut( value 
			= "execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".EntityBiLinkCollectionProxyImplAspect.LockContainer.*(..)) " 
			+ "&& target( com.newmainsoftech.dao.entity.relation.EntityBiLinkCollectionProxyImpl)")
	public static void pointcutAtExecutionOfLockContainerMethod() {};
	
	private static ThreadLocal<Boolean> readLockFlagForCurrentThread = new ThreadLocal<Boolean>();  
		protected static ThreadLocal<Boolean> getReadLockFlagForCurrentThread() {
			return readLockFlagForCurrentThread;
		}
		protected static void setReadLockedByCurrentThread( boolean isReadLocked) {
			getReadLockFlagForCurrentThread().set( isReadLocked);
		}
		protected static boolean isReadLockedByCurrentThread() {
			final Boolean readLockFlag = getReadLockFlagForCurrentThread().get();
				if ( readLockFlag == null) {
					setReadLockedByCurrentThread( false);
					return false;
				}
			return readLockFlag;
		}
		protected static void removeReadLockFlagForCurrentThread() {
			getReadLockFlagForCurrentThread().remove();
		}
		
	protected Object doAroundExecutionOfReadSynchronizingMethod( 
			final LockContainer lockContainer, final ProceedingJoinPoint proceedingJoinPoint) 
	throws Throwable {
		final ReentrantReadWriteLock lock = lockContainer.getLock();
			if ( lock.isWriteLockedByCurrentThread()) {
				// No point for spending additional time for acquiring read lock.
				return Util.proceed( proceedingJoinPoint);
			}
			
		if ( isReadLockedByCurrentThread()) { 
					// No point for spending additional time for acquiring read lock again.
					// Do not mix this condition with above if-block with using 
					// lock.isWriteLockedByCurrentThread, and also this condition should come after 
					// that if-block, in order to remove ThreadLocal properly. 
			return Util.proceed( proceedingJoinPoint);
		}
		final long acquireTimeOut = lockContainer.getReadLockAcquireTimeOut();
		final ReadLock readLock = lock.readLock();
		boolean isLocked = false;
			try {
				isLocked 
				= readLock.tryLock( acquireTimeOut, TimeUnit.MILLISECONDS);
			} 
			catch ( InterruptedException exception) {
				Thread.currentThread().interrupt();
			}
		if ( isLocked) {
			try {
				setReadLockedByCurrentThread( true);
				return Util.proceed( proceedingJoinPoint);
			}
			finally {
				readLock.unlock();
				removeReadLockFlagForCurrentThread();
			}
		}
		else {
			removeReadLockFlagForCurrentThread();
			throw new IllegalMonitorStateException(
					String.format(
							"Within %1$d [ms], could not acquire read-lock to proceed \"%2$s\".",
							acquireTimeOut, 
							proceedingJoinPoint)
					);
		}
	}
		
	@Around( value 
			= "pointcutAtExecutionOfReadSynchronizingMethod() " 
			+ "&& !pointcutForCflowbelowEntityBiLinkCollectionProxyImplConstructioin() " 
			+ "&& !pointcutAtExecutionOfLockContainerMethod()") // Yes, necessary to include mixing interface condition
	public Object aroundExecutionOfReadSynchronizingMethod( final ProceedingJoinPoint proceedingJoinPoint) 
	throws Throwable {
		final Object target = proceedingJoinPoint.getTarget();
			if ( !EntityBiLinkCollectionProxyImpl.class.isInstance( target)) {
				throw new IllegalMonitorStateException(
						String.format(
								"Unexpected to cross-cut join-point in other than %1$s type instance: %2$s",
								EntityBiLinkCollectionProxyImpl.class.getSimpleName(),
								proceedingJoinPoint)
						);
			}
		
		final LockContainer lockContainer = (LockContainer)target;
		
		return doAroundExecutionOfReadSynchronizingMethod( lockContainer, proceedingJoinPoint);
	}
	
	// For iterator -------------------------------------------------------------------------------
	@Pointcut( value 
			= "execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".HoldeeIteratorCramptedProxyImpl.interRemove(..))")
	public static void pointcutAtExecutionOfIteratorWriteSynchronizingMethod() {}
	
	@Around( value = "pointcutAtExecutionOfIteratorWriteSynchronizingMethod()")
	@SuppressWarnings( "rawtypes")
	public Object aroundExecutionOfIteratorWriteSynchronizingMethod( 
			final ProceedingJoinPoint proceedingJoinPoint) 
	throws Throwable {
		final HoldeeIteratorCramptedProxyImpl iteratorProxy 
		= (HoldeeIteratorCramptedProxyImpl)proceedingJoinPoint.getTarget();
		final LockContainer lockContainer = (LockContainer)(iteratorProxy.getCollectionProxyImpl());
		
		return doAroundExecutionOfWriteSynchronizingMethod( lockContainer, proceedingJoinPoint);
	}
	
	@Pointcut( value 
			= "execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".HoldeeIteratorCramptedProxyImpl.interNext(..)) " 
			+ "|| execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".HoldeeIteratorCramptedProxyImpl.invokeDelegateIteratorNonExclusiveMethod(..)) " 
			+ "|| (execution( * com.newmainsoftech.dao.entity.relation" 
					+ ".HoldeeIteratorCramptedProxyImpl.getCollectionProxyImpl()) " 
			+ "&& !cflowbelow( adviceexecution()))")
	public static void pointcutAtExecutionOfIteratorReadSynchronizingMethod() {}
	
	@Around( value = "pointcutAtExecutionOfIteratorReadSynchronizingMethod()")
	@SuppressWarnings( "rawtypes")
	public Object aroundExecutionOfIteratorReadSynchronizingMethod( 
			final ProceedingJoinPoint proceedingJoinPoint) 
	throws Throwable {
		final HoldeeIteratorCramptedProxyImpl iteratorProxy 
		= (HoldeeIteratorCramptedProxyImpl)proceedingJoinPoint.getTarget();
		final LockContainer lockContainer = (LockContainer)(iteratorProxy.getCollectionProxyImpl());
		
		return doAroundExecutionOfReadSynchronizingMethod( lockContainer, proceedingJoinPoint);
	}
	// --------------------------------------------------------------------------------------------
}
